//-
// ==========================================================================
// Copyright 1995,2006,2008 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
// ==========================================================================
//+

// Example Plugin: circleNode.cpp
//
// This plug-in is an example of a user-defined dependency graph node.
// It takes a number as input (such as time) and generates two output
// numbers one which describes a sine curve as the input varies and
// one that generates a cosine curve. If these two are hooked up to
// the x and z translation attributes of an object the object will describe
// move through a circle in the xz plane as time is changed.
//
// Executing the command "source circleNode" will run the MEL script which will
// create a new "Circle" menu with a single item. Selecting this will build
// a simple model (a sphere which follows a circular path) which can be played back,
// by clicking on the "play" icon on the time slider.  Note: the circleNode
// plugin needs to be loaded before the "Circle" menu item can be executed
// properly.
//
// The node has two additional attributes which can be changed to affect
// the animation, "scale" which defines the size of the circular path, and
// "frames" which defines the number of frames required for a complete circuit
// of the path. Either of these can be hooked up to other nodes, or can
// be simply set via the MEL command "setAttr" operating on the circle node
// "circleNode1" created by the MEL script. For example, "setAttr circleNode1.scale #"
// will change the size of the circle and "setAttr circleNode1.frames #"
// will cause objects to complete a circle in indicated number of frames.


#include <string.h>
#include <maya/MIOStream.h>

#include <maya/MPxNode.h> 

#include <maya/MString.h> 
#include <maya/MTypeId.h> 
#include <maya/MPlug.h>

#include <maya/MFnNumericAttribute.h>
#include <maya/MVector.h>

#include <maya/MFnPlugin.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>

#include <math.h>

// The circle class defines the attributes
// and methods necessary for the circleNode plugin
//
class circle : public MPxNode
{
public:
						circle();
	virtual				~circle(); 

	virtual MStatus		compute( const MPlug& plug, MDataBlock& data );

	static	void*		creator();
	static	MStatus		initialize();
 
public:
	static	MObject		input;		// The input value.
	static	MObject		sOutput;	// The sinusoidal output value.
	static	MObject		cOutput;	// The cosinusoidal output value.
	static	MObject		frames;		// Number of frames for one circle.
	static	MObject		scale;		// Size of circle.
	static	MTypeId		id;
};

MTypeId     circle::id( 0x80005 );
MObject     circle::input;        
MObject     circle::sOutput;       
MObject     circle::cOutput;       
MObject	    circle::frames;
MObject 	circle::scale;

// The creator method creates an instance of the circleNode class
// and is the first method called by Maya
// when a circleNode needs to be created.
//
void* circle::creator()
{
	return new circle();
}

// The initialize routine is called after the node has been created.
// It sets up the input and output attributes and adds them to the node.
// Finally the dependencies are arranged so that when the inputs
// change Maya knowns to call compute to recalculate the output values.
// The inputs are: input, scale, frames
// The outputs are: sineOutput, cosineOutput
//
MStatus circle::initialize()
{
	MFnNumericAttribute nAttr;
	MStatus				stat;

	// Setup the input attributes
	//
	input = nAttr.create( "input", "in", MFnNumericData::kFloat, 0.0,
			&stat );
	CHECK_MSTATUS( stat );
 	CHECK_MSTATUS( nAttr.setStorable( true ) );

	scale = nAttr.create( "scale", "sc", MFnNumericData::kFloat, 10.0,
			&stat );
	CHECK_MSTATUS( stat );
	CHECK_MSTATUS( nAttr.setStorable( true ) );

	frames = nAttr.create( "frames", "fr", MFnNumericData::kFloat, 48.0,
			&stat );
	CHECK_MSTATUS( stat );
	CHECK_MSTATUS( nAttr.setStorable( true ) );

	// Setup the output attributes
	//
	sOutput = nAttr.create( "sineOutput", "so", MFnNumericData::kFloat,
			0.0, &stat );
	CHECK_MSTATUS( stat );
	CHECK_MSTATUS( nAttr.setWritable( false ) );
	CHECK_MSTATUS( nAttr.setStorable( false ) );

	cOutput = nAttr.create( "cosineOutput", "co", MFnNumericData::kFloat,
			0.0, &stat );
	CHECK_MSTATUS( stat );
	CHECK_MSTATUS( nAttr.setWritable( false ) );
	CHECK_MSTATUS( nAttr.setStorable( false ) );

	// Add the attributes to the node
	//
	CHECK_MSTATUS( addAttribute( input ) );
	CHECK_MSTATUS( addAttribute( scale ) );
	CHECK_MSTATUS( addAttribute( frames ) );
	CHECK_MSTATUS( addAttribute( sOutput ) );
	CHECK_MSTATUS( addAttribute( cOutput ) );

	// Set the attribute dependencies
	//
	CHECK_MSTATUS( attributeAffects( input, sOutput ) );
	CHECK_MSTATUS( attributeAffects( input, cOutput ) );
	CHECK_MSTATUS( attributeAffects( scale, sOutput ) );
	CHECK_MSTATUS( attributeAffects( scale, cOutput ) );
	CHECK_MSTATUS( attributeAffects( frames, sOutput ) );
	CHECK_MSTATUS( attributeAffects( frames, cOutput ) );

	return MS::kSuccess;
} 

// The constructor does nothing
//
circle::circle() {}

// The destructor does nothing
//
circle::~circle() {}

// The compute method is called by Maya when the input values
// change and the output values need to be recomputed.
// The input values are read then using sinf() and cosf()
// the output values are stored on the output plugs.
//
MStatus circle::compute (const MPlug& plug, MDataBlock& data)
{
	
	MStatus returnStatus;
 
	// Check that the requested recompute is one of the output values
	//
	if (plug == sOutput || plug == cOutput) {
		// Read the input values
		//
		MDataHandle inputData = data.inputValue (input, &returnStatus);
		CHECK_MSTATUS( returnStatus );
		MDataHandle scaleData = data.inputValue (scale, &returnStatus);
		CHECK_MSTATUS( returnStatus );
		MDataHandle framesData = data.inputValue (frames, &returnStatus);
		CHECK_MSTATUS( returnStatus );

		// Compute the output values
		//
		float currentFrame = inputData.asFloat();
		float scaleFactor  = scaleData.asFloat();
		float framesPerCircle = framesData.asFloat();
		float angle = 6.2831853f * (currentFrame/framesPerCircle);
		float sinResult = sinf (angle) * scaleFactor;
		float cosResult = cosf (angle) * scaleFactor;

		// Store them on the output plugs
		//
		MDataHandle sinHandle = data.outputValue( circle::sOutput,
				&returnStatus );
		CHECK_MSTATUS( returnStatus );
		MDataHandle cosHandle = data.outputValue( circle::cOutput,
				&returnStatus );
		CHECK_MSTATUS( returnStatus );
		sinHandle.set( sinResult );
		cosHandle.set( cosResult );
		CHECK_MSTATUS( data.setClean( plug ) );
	} else {
		return MS::kUnknownParameter;
	}

	return MS::kSuccess;
}

// The initializePlugin method is called by Maya when the circleNode
// plugin is loaded.  It registers the circleNode which provides
// Maya with the creator and initialize methods to be called when
// a circleNode is created.
//
MStatus initializePlugin ( MObject obj )
{ 
	MStatus   status;
	MFnPlugin plugin( obj, PLUGIN_COMPANY, "4.5", "Any");

	status = plugin.registerNode( "circle", circle::id,
						  circle::creator, circle::initialize );
	if (!status) {
		status.perror("registerNode");
		return status;
	}

	return status;
}

// The unitializePlugin is called when Maya needs to unload the plugin.
// It basically does the opposite of initialize by calling
// the deregisterCommand to remove it.
//
MStatus uninitializePlugin( MObject obj)
{
	MStatus   status;
	MFnPlugin plugin( obj );

	status = plugin.deregisterNode( circle::id );
	if (!status) {
		status.perror("deregisterNode");
		return status;
	}

	return status;
}
